home *** CD-ROM | disk | FTP | other *** search
/ Isometric Game Programming with DirectX 7.0 / Isometric Game Programming.iso / directx / dxf / samples / multimedia / directshow / misc / sysenum / sysenumdlg.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2000-10-02  |  17.2 KB  |  635 lines

  1. //------------------------------------------------------------------------------
  2. // File: SysEnumDlg.cpp
  3. //
  4. // Desc: DirectShow sample code - implementation of dialog for device
  5. //       enumeration.
  6. //
  7. // Copyright (c) 2000, Microsoft Corporation.  All rights reserved.
  8. //------------------------------------------------------------------------------
  9.  
  10.  
  11. #include "stdafx.h"
  12. #include "SysEnum.h"
  13. #include "SysEnumDlg.h"
  14.  
  15. #ifdef _DEBUG
  16. #define new DEBUG_NEW
  17. #undef THIS_FILE
  18. static char THIS_FILE[] = __FILE__;
  19. #endif
  20.  
  21. /////////////////////////////////////////////////////////////////////////////
  22. // CAboutDlg dialog used for App About
  23.  
  24. class CAboutDlg : public CDialog
  25. {
  26. public:
  27.     CAboutDlg();
  28.  
  29. // Dialog Data
  30.     //{{AFX_DATA(CAboutDlg)
  31.     enum { IDD = IDD_ABOUTBOX };
  32.     //}}AFX_DATA
  33.  
  34.     // ClassWizard generated virtual function overrides
  35.     //{{AFX_VIRTUAL(CAboutDlg)
  36.     protected:
  37.     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
  38.     //}}AFX_VIRTUAL
  39.  
  40. // Implementation
  41. protected:
  42.     //{{AFX_MSG(CAboutDlg)
  43.     //}}AFX_MSG
  44.     DECLARE_MESSAGE_MAP()
  45. };
  46.  
  47. CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
  48. {
  49.     //{{AFX_DATA_INIT(CAboutDlg)
  50.     //}}AFX_DATA_INIT
  51. }
  52.  
  53. void CAboutDlg::DoDataExchange(CDataExchange* pDX)
  54. {
  55.     CDialog::DoDataExchange(pDX);
  56.     //{{AFX_DATA_MAP(CAboutDlg)
  57.     //}}AFX_DATA_MAP
  58. }
  59.  
  60. BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
  61.     //{{AFX_MSG_MAP(CAboutDlg)
  62.         // No message handlers
  63.     //}}AFX_MSG_MAP
  64. END_MESSAGE_MAP()
  65.  
  66. /////////////////////////////////////////////////////////////////////////////
  67. // CSysEnumDlg dialog
  68.  
  69. CSysEnumDlg::CSysEnumDlg(CWnd* pParent /*=NULL*/)
  70.     : CDialog(CSysEnumDlg::IDD, pParent)
  71. {
  72.     //{{AFX_DATA_INIT(CSysEnumDlg)
  73.     m_bShowAllCategories = FALSE;
  74.     //}}AFX_DATA_INIT
  75.     // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
  76.     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
  77. }
  78.  
  79. void CSysEnumDlg::DoDataExchange(CDataExchange* pDX)
  80. {
  81.     CDialog::DoDataExchange(pDX);
  82.     //{{AFX_DATA_MAP(CSysEnumDlg)
  83.     DDX_Control(pDX, IDC_STATIC_FILENAME, m_StrFilename);
  84.     DDX_Control(pDX, IDC_STATIC_FILTERS, m_StrFilters);
  85.     DDX_Control(pDX, IDC_STATIC_CLASSES, m_StrClasses);
  86.     DDX_Control(pDX, IDC_LIST_FILTERS, m_FilterList);
  87.     DDX_Control(pDX, IDC_LIST_DEVICES, m_DeviceList);
  88.     DDX_Check(pDX, IDC_CHECK_SHOWALL, m_bShowAllCategories);
  89.     //}}AFX_DATA_MAP
  90. }
  91.  
  92. BEGIN_MESSAGE_MAP(CSysEnumDlg, CDialog)
  93.     //{{AFX_MSG_MAP(CSysEnumDlg)
  94.     ON_WM_SYSCOMMAND()
  95.     ON_WM_PAINT()
  96.     ON_WM_QUERYDRAGICON()
  97.     ON_LBN_SELCHANGE(IDC_LIST_DEVICES, OnSelchangeListDevices)
  98.     ON_WM_CLOSE()
  99.     ON_BN_CLICKED(IDC_CHECK_SHOWALL, OnCheckShowall)
  100.     ON_LBN_SELCHANGE(IDC_LIST_FILTERS, OnSelchangeListFilters)
  101.     //}}AFX_MSG_MAP
  102. END_MESSAGE_MAP()
  103.  
  104.  
  105. /////////////////////////////////////////////////////////////////////////////
  106. // CSysEnumDlg message handlers
  107.  
  108. void CSysEnumDlg::OnSysCommand(UINT nID, LPARAM lParam)
  109. {
  110.     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
  111.     {
  112.         CAboutDlg dlgAbout;
  113.         dlgAbout.DoModal();
  114.     }
  115.     else
  116.     {
  117.         CDialog::OnSysCommand(nID, lParam);
  118.     }
  119. }
  120.  
  121.  
  122. // If you add a minimize button to your dialog, you will need the code below
  123. //  to draw the icon.  For MFC applications using the document/view model,
  124. //  this is automatically done for you by the framework.
  125.  
  126. void CSysEnumDlg::OnPaint() 
  127. {
  128.     if (IsIconic())
  129.     {
  130.         CPaintDC dc(this); // device context for painting
  131.  
  132.         SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
  133.  
  134.         // Center icon in client rectangle
  135.         int cxIcon = GetSystemMetrics(SM_CXICON);
  136.         int cyIcon = GetSystemMetrics(SM_CYICON);
  137.         CRect rect;
  138.         GetClientRect(&rect);
  139.         int x = (rect.Width() - cxIcon + 1) / 2;
  140.         int y = (rect.Height() - cyIcon + 1) / 2;
  141.  
  142.         // Draw the icon
  143.         dc.DrawIcon(x, y, m_hIcon);
  144.     }
  145.     else
  146.     {
  147.         CDialog::OnPaint();
  148.     }
  149. }
  150.  
  151. // The system calls this to obtain the cursor to display while the user drags
  152. //  the minimized window.
  153. HCURSOR CSysEnumDlg::OnQueryDragIcon()
  154. {
  155.     return (HCURSOR) m_hIcon;
  156. }
  157.  
  158.  
  159. BOOL CSysEnumDlg::OnInitDialog()
  160. {
  161.     HRESULT hr;
  162.  
  163.     CDialog::OnInitDialog();
  164.  
  165.     // Add "About..." menu item to system menu.
  166.  
  167.     // IDM_ABOUTBOX must be in the system command range.
  168.     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
  169.     ASSERT(IDM_ABOUTBOX < 0xF000);
  170.  
  171.     CMenu* pSysMenu = GetSystemMenu(FALSE);
  172.     if (pSysMenu != NULL)
  173.     {
  174.         CString strAboutMenu;
  175.         strAboutMenu.LoadString(IDS_ABOUTBOX);
  176.         if (!strAboutMenu.IsEmpty())
  177.         {
  178.             pSysMenu->AppendMenu(MF_SEPARATOR);
  179.             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  180.         }
  181.     }
  182.  
  183.     // Set the icon for this dialog.  The framework does this automatically
  184.     //  when the application's main window is not a dialog
  185.     SetIcon(m_hIcon, TRUE);            // Set big icon
  186.     SetIcon(m_hIcon, FALSE);        // Set small icon
  187.     
  188.     ////////////////////////////////////////////////////////////////////////
  189.     //
  190.     //  DirectShow-specific initialization code
  191.  
  192.     CoInitialize(NULL);
  193.  
  194.     // Instantiate the system device enumerator
  195.     m_pSysDevEnum = NULL;
  196.  
  197.     hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, 
  198.                           CLSCTX_INPROC, IID_ICreateDevEnum, 
  199.                           (void **)&m_pSysDevEnum);
  200.     if FAILED(hr)
  201.     {
  202.         CoUninitialize();
  203.         return FALSE;
  204.     }
  205.  
  206.     // By default, only enumerate subset of categories listed in docs
  207.     m_bShowAllCategories = FALSE;
  208.     FillCategoryList();
  209.  
  210.     return TRUE;  // return TRUE  unless you set the focus to a control
  211. }
  212.  
  213.  
  214. void CSysEnumDlg::OnCheckShowall() 
  215. {
  216.     // Toggle category type and redraw the category list
  217.     m_bShowAllCategories ^= 1;
  218.     FillCategoryList();
  219.     SetNumFilters(0);
  220. }
  221.  
  222.  
  223. void CSysEnumDlg::FillCategoryList(void)
  224. {
  225.     // Clear listboxes
  226.     ClearDeviceList();
  227.     ClearFilterList();
  228.  
  229.     if (m_bShowAllCategories)
  230.     {
  231.         // Emulate the behavior of GraphEdit by enumerating all 
  232.         // categories in the system
  233.         DisplayFullCategorySet();
  234.     }
  235.     else
  236.     {
  237.         // Fill the category list box with the categories to display,
  238.         // using the names stored in the CATEGORY_INFO array.
  239.         // SysEnumDlg.H for a category description.
  240.         for (int i=0; i < NUM_CATEGORIES; i++)
  241.         {
  242.             m_DeviceList.AddString(categories[i].szName);
  243.         }
  244.  
  245.         // Update listbox title with number of classes
  246.         SetNumClasses(NUM_CATEGORIES);
  247.     }
  248. }
  249.  
  250.  
  251. void CSysEnumDlg::SetNumClasses(int nClasses)
  252. {
  253.     TCHAR szClasses[64];
  254.  
  255.     wsprintf(szClasses, TEXT("%s (%d found)\0"), STR_CLASSES, nClasses);
  256.     m_StrClasses.SetWindowText(szClasses);
  257. }
  258.  
  259. void CSysEnumDlg::SetNumFilters(int nFilters)
  260. {
  261.     TCHAR szFilters[64];
  262.  
  263.     if (nFilters)
  264.         wsprintf(szFilters, TEXT("%s (%d found)\0"), STR_FILTERS, nFilters);
  265.     else
  266.         wsprintf(szFilters, TEXT("%s\0"), STR_FILTERS);
  267.  
  268.     m_StrFilters.SetWindowText(szFilters);
  269. }
  270.  
  271.  
  272. void CSysEnumDlg::DisplayFullCategorySet(void)
  273. {
  274.     HRESULT hr;
  275.     IEnumMoniker *pEmCat = 0;
  276.     ICreateDevEnum *pCreateDevEnum = NULL;
  277.     int nClasses=0;
  278.  
  279.     // Create an enumerator
  280.     hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  281.                           IID_ICreateDevEnum, (void**)&pCreateDevEnum);
  282.     ASSERT(SUCCEEDED(hr));
  283.     if (FAILED(hr))
  284.         return;
  285.  
  286.     // Use the meta-category that contains a list of all categories.
  287.     // This emulates the behavior of GraphEdit.
  288.     hr = pCreateDevEnum->CreateClassEnumerator(
  289.                          CLSID_ActiveMovieCategories, &pEmCat, 0);
  290.     ASSERT(SUCCEEDED(hr));
  291.  
  292.     if(hr == S_OK)
  293.     {
  294.         IMoniker *pMCat;
  295.         ULONG cFetched;
  296.  
  297.         // Enumerate over every category
  298.         while(hr = pEmCat->Next(1, &pMCat, &cFetched),
  299.               hr == S_OK)
  300.         {
  301.             IPropertyBag *pPropBag;
  302.  
  303.             // Associate moniker with a file
  304.             hr = pMCat->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
  305.             if(SUCCEEDED(hr))
  306.             {
  307.                 VARIANT varCatClsid;
  308.                 varCatClsid.vt = VT_BSTR;
  309.  
  310.                 // Read CLSID string from property bag
  311.                 hr = pPropBag->Read(L"CLSID", &varCatClsid, 0);
  312.                 if(SUCCEEDED(hr))
  313.                 {
  314.                     CLSID clsidCat;
  315.  
  316.                     if(CLSIDFromString(varCatClsid.bstrVal, &clsidCat) == S_OK)
  317.                     {
  318.                         // Use the guid if we can't get the name
  319.                         WCHAR *wszCatName;
  320.                         TCHAR szCatDesc[MAX_PATH];
  321.  
  322.                         VARIANT varCatName;
  323.                         varCatName.vt = VT_BSTR;
  324.  
  325.                         // Read filter name
  326.                         hr = pPropBag->Read(L"FriendlyName", &varCatName, 0);
  327.                         if(SUCCEEDED(hr))
  328.                             wszCatName = varCatName.bstrVal;
  329.                         else
  330.                             wszCatName = varCatClsid.bstrVal;
  331.  
  332.                         WideCharToMultiByte(
  333.                                 CP_ACP, 0, wszCatName, -1,
  334.                                 szCatDesc, sizeof(szCatDesc), 0, 0);
  335.  
  336.                         if(SUCCEEDED(hr))
  337.                             SysFreeString(varCatName.bstrVal);
  338.  
  339.                         // Add category name and CLSID to list box
  340.                         AddFilterCategory(szCatDesc, &clsidCat);
  341.                         nClasses++;
  342.                     }
  343.  
  344.                     SysFreeString(varCatClsid.bstrVal);
  345.                 }
  346.  
  347.                 pPropBag->Release();
  348.             }
  349.             else
  350.             {
  351.                 break;
  352.             }
  353.  
  354.             pMCat->Release();
  355.         } // for loop
  356.  
  357.         pEmCat->Release();
  358.     }
  359.  
  360.     pCreateDevEnum->Release();
  361.  
  362.     // Update listbox title with number of classes
  363.     SetNumClasses(nClasses);
  364. }
  365.  
  366.  
  367. void CSysEnumDlg::AddFilterCategory(
  368.     const TCHAR *szCatDesc,
  369.     const GUID *pCatGuid)
  370. {
  371.     // Allocate a new CLSID, whose pointer will be stored in 
  372.     // the listbox.  When the listbox is cleared, these will be deleted.
  373.     CLSID *pclsid = new CLSID;
  374.     *pclsid = *pCatGuid;
  375.  
  376.     // Add the category name and a pointer to its CLSID to the list box
  377.     int nSuccess  = m_DeviceList.AddString(szCatDesc);
  378.     int nIndexNew = m_DeviceList.FindStringExact(-1, szCatDesc);
  379.     nSuccess = m_DeviceList.SetItemDataPtr(nIndexNew, pclsid);
  380. }
  381.  
  382. void CSysEnumDlg::AddFilter(
  383.     const TCHAR *szFilterName,
  384.     const GUID *pCatGuid)
  385. {
  386.     // Allocate a new CLSID, whose pointer will be stored in 
  387.     // the listbox.  When the listbox is cleared, these will be deleted.
  388.     CLSID *pclsid = new CLSID;
  389.     *pclsid = *pCatGuid;
  390.  
  391.     // Add the category name and a pointer to its CLSID to the list box
  392.     int nSuccess  = m_FilterList.AddString(szFilterName);
  393.     int nIndexNew = m_FilterList.FindStringExact(-1, szFilterName);
  394.     nSuccess = m_FilterList.SetItemDataPtr(nIndexNew, pclsid);
  395. }
  396.  
  397.  
  398. void CSysEnumDlg::ClearDeviceList(void)
  399. {
  400.     CLSID *pStoredId = NULL;
  401.     
  402.     int nCount = m_DeviceList.GetCount();
  403.  
  404.     // Delete any CLSID pointers that were stored in the listbox item data
  405.     for (int i=0; i < nCount; i++)
  406.     {
  407.         pStoredId = (CLSID *) m_DeviceList.GetItemDataPtr(i);
  408.         if (pStoredId != 0)
  409.         {
  410.             delete pStoredId;
  411.             pStoredId = NULL;
  412.         }
  413.     }
  414.  
  415.     // Clean up
  416.     m_DeviceList.ResetContent();
  417.     SetNumClasses(0);
  418. }
  419.  
  420. void CSysEnumDlg::ClearFilterList(void)
  421. {
  422.     CLSID *pStoredId = NULL;
  423.     
  424.     int nCount = m_FilterList.GetCount();
  425.  
  426.     // Delete any CLSID pointers that were stored in the listbox item data
  427.     for (int i=0; i < nCount; i++)
  428.     {
  429.         pStoredId = (CLSID *) m_FilterList.GetItemDataPtr(i);
  430.         if (pStoredId != 0)
  431.         {
  432.             delete pStoredId;
  433.             pStoredId = NULL;
  434.         }
  435.     }
  436.  
  437.     // Clean up
  438.     m_FilterList.ResetContent();
  439.     SetNumFilters(0);
  440.     m_StrFilename.SetWindowText("<No filter selected>");
  441. }
  442.  
  443.  
  444. void CSysEnumDlg::OnSelchangeListDevices() 
  445. {
  446.     HRESULT hr;    
  447.     IEnumMoniker *pEnumCat = NULL;
  448.  
  449.     // Get the currently selected category name
  450.     int nItem = m_DeviceList.GetCurSel();
  451.     const CLSID *clsid;
  452.     
  453.     if (m_bShowAllCategories)
  454.     {
  455.         // Read the CLSID pointer from the list box's item data
  456.         clsid = (CLSID *) m_DeviceList.GetItemDataPtr(nItem);
  457.     }
  458.     else
  459.     {
  460.         // Read the CLSID pointer from our hard-coded array of
  461.         // documented filter categories
  462.         clsid = categories[nItem].pclsid;
  463.     }
  464.  
  465.     //
  466.     // WARNING!
  467.     //
  468.     // Some third-party filters throw an exception (int 3) during enumeration
  469.     // on Debug builds, often due to heap corruption in RtlFreeHeap().
  470.     // This is not an issue on Release builds.
  471.     //
  472.  
  473.     // Enumerate all filters of the selected category  
  474.     hr = m_pSysDevEnum->CreateClassEnumerator(*clsid, &pEnumCat, 0);
  475.     ASSERT(SUCCEEDED(hr));
  476.     if FAILED(hr)
  477.         return;
  478.  
  479.     // Enumerate all filters using the category enumerator
  480.     hr = EnumFilters(pEnumCat);
  481.  
  482.     SAFE_RELEASE(pEnumCat);
  483. }
  484.  
  485.  
  486. HRESULT CSysEnumDlg::EnumFilters(IEnumMoniker *pEnumCat)
  487. {
  488.     HRESULT hr=S_OK;
  489.     IMoniker *pMoniker;
  490.     ULONG cFetched;
  491.     VARIANT varName={0};
  492.     int nFilters=0;
  493.  
  494.     // Clear the current filter list
  495.     ClearFilterList();
  496.  
  497.     // If there are no filters of a requested type, show default string
  498.     if (!pEnumCat)
  499.     {
  500.         m_FilterList.AddString("<< No entries >>");
  501.         SetNumFilters(nFilters);
  502.         return S_FALSE;
  503.     }
  504.  
  505.     // Enumerate all items associated with the moniker
  506.     while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
  507.     {
  508.         IPropertyBag *pPropBag;
  509.         ASSERT(pMoniker);
  510.  
  511.         // Associate moniker with a file
  512.         hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
  513.                                     (void **)&pPropBag);
  514.         ASSERT(SUCCEEDED(hr));
  515.         ASSERT(pPropBag);
  516.         if (FAILED(hr))
  517.             continue;
  518.  
  519.         // Read filter name from property bag
  520.         varName.vt = VT_BSTR;
  521.         hr = pPropBag->Read(L"FriendlyName", &varName, 0);
  522.         if (FAILED(hr))
  523.             continue;
  524.  
  525.         // Get filter name (converting BSTR name to a CString)
  526.         CString str(varName.bstrVal);
  527.         SysFreeString(varName.bstrVal);
  528.         nFilters++;
  529.  
  530.         // Read filter's CLSID from property bag.  This CLSID string will be
  531.         // converted to a binary CLSID and passed to AddFilter(), which will
  532.         // add the filter's name to the listbox and its CLSID to the listbox
  533.         // item's DataPtr item.  When the user clicks on a filter name in
  534.         // the listbox, we'll read the stored CLSID, convert it to a string,
  535.         // and use it to find the filter's filename in the registry.
  536.         VARIANT varFilterClsid;
  537.         varFilterClsid.vt = VT_BSTR;
  538.  
  539.         // Read CLSID string from property bag
  540.         hr = pPropBag->Read(L"CLSID", &varFilterClsid, 0);
  541.         if(SUCCEEDED(hr))
  542.         {
  543.             CLSID clsidFilter;
  544.  
  545.             // Add filter name and CLSID to listbox
  546.             if(CLSIDFromString(varFilterClsid.bstrVal, &clsidFilter) == S_OK)
  547.             {
  548.                 AddFilter(str, &clsidFilter);
  549.             }
  550.  
  551.             SysFreeString(varFilterClsid.bstrVal);
  552.         }
  553.        
  554.         // Cleanup interfaces
  555.         SAFE_RELEASE(pPropBag);
  556.         SAFE_RELEASE(pMoniker);
  557.     }
  558.  
  559.     // Update count of enumerated filters
  560.     SetNumFilters(nFilters);
  561.     return hr;
  562. }
  563.  
  564.  
  565. void CSysEnumDlg::OnSelchangeListFilters() 
  566. {
  567.     const CLSID *clsid;
  568.  
  569.     // Get the currently selected category name
  570.     int nItem = m_FilterList.GetCurSel();
  571.     
  572.     // Read the CLSID pointer from the list box's item data
  573.     clsid = (CLSID *) m_FilterList.GetItemDataPtr(nItem);
  574.  
  575.     // Find the filter filename in the registry (by CLSID)
  576.     if (clsid != 0)
  577.         ShowFilenameByCLSID(*clsid);
  578. }
  579.  
  580.  
  581. void CSysEnumDlg::ShowFilenameByCLSID(REFCLSID clsid)
  582. {
  583.     HRESULT hr;
  584.     LPOLESTR strCLSID;
  585.  
  586.     // Convert binary CLSID to a readable version
  587.     hr = StringFromCLSID(clsid, &strCLSID);
  588.     if(SUCCEEDED(hr))
  589.     {
  590.         TCHAR szKey[512];
  591.         CString strQuery(strCLSID);
  592.  
  593.         // Create key name for reading filename registry
  594.         wsprintf(szKey, TEXT("Software\\Classes\\CLSID\\%s\\InprocServer32\0"),
  595.                  strQuery);
  596.  
  597.         // Free memory associated with strCLSID (allocated in StringFromCLSID)
  598.         CoTaskMemFree(strCLSID);
  599.  
  600.         HKEY hkeyFilter=0;
  601.         DWORD dwSize=MAX_PATH;
  602.         BYTE szFilename[MAX_PATH];
  603.         int rc=0;
  604.  
  605.         // Open the CLSID key that contains information about the filter
  606.         rc = RegOpenKey(HKEY_LOCAL_MACHINE, szKey, &hkeyFilter);
  607.         if (rc == ERROR_SUCCESS)
  608.         {
  609.             rc = RegQueryValueEx(hkeyFilter, NULL,  // Read (Default) value
  610.                                  NULL, NULL, szFilename, &dwSize);
  611.  
  612.             if (rc == ERROR_SUCCESS)
  613.                 m_StrFilename.SetWindowText((char *) szFilename);
  614.  
  615.             rc = RegCloseKey(hkeyFilter);
  616.         }
  617.     }
  618. }
  619.  
  620.  
  621. void CSysEnumDlg::OnClose() 
  622. {
  623.     // Free any stored CLSID pointers (in listbox item data ptr area)
  624.     ClearFilterList();
  625.     ClearDeviceList();
  626.  
  627.     // Release system device enumerator and close COM
  628.     SAFE_RELEASE(m_pSysDevEnum);
  629.     CoUninitialize();
  630.     
  631.     CDialog::OnClose();
  632. }
  633.  
  634.  
  635.